<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title></title>
    <link rel="stylesheet" href="/css/bundle" />
    <style>
        :root {
            --primary-color: #4361ee;
            --secondary-color: #3f37c9;
            --accent-color: #4cc9f0;
            --success-color: #4bb543;
            --warning-color: #ff9f1c;
            --danger-color: #e71d36;
            --light-bg: #f8f9fa;
            --dark-text: #212529;
            --light-text: #6c757d;
            --card-shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
            --hover-shadow: 0 15px 35px rgba(0, 0, 0, 0.12);
        }

        body {
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8f0 100%);
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            color: var(--dark-text);
            padding-top: 80px;
        }

        .header-section {
            text-align: center;
            margin-bottom: 40px;
        }

        .main-title {
            font-weight: 700;
            color: var(--primary-color);
            margin-bottom: 10px;
            font-size: 2.5rem;
        }

        .subtitle {
            color: var(--light-text);
            font-size: 1.1rem;
            max-width: 600px;
            margin: 0 auto;
        }

        .card {
            box-shadow: var(--card-shadow);
            margin-bottom: 25px;
            border: none;
            border-radius: 12px;
            transition: transform 0.3s, box-shadow 0.3s;
            overflow: hidden;
        }

        .card:hover {
            transform: translateY(-5px);
            box-shadow: var(--hover-shadow);
        }

        .card-header {
            background: linear-gradient(120deg, var(--primary-color), var(--secondary-color));
            color: white;
            border: none;
            padding: 15px 20px;
            font-weight: 600;
        }

        .file-drop-area {
            border: 2px dashed #cbd5e0;
            border-radius: 10px;
            padding: 20px;
            text-align: center;
            transition: all 0.3s;
            cursor: pointer;
            background-color: rgba(255, 255, 255, 0.7);
            position: relative;
        }

        .file-drop-area:hover,
        .file-drop-area.active {
            border-color: var(--primary-color);
            background-color: rgba(67, 97, 238, 0.05);
        }

        .file-drop-area i {
            font-size: 3.5rem;
            margin-bottom: 15px;
            color: var(--primary-color);
        }

        .task-row {
            transition: all 0.3s;
        }

        .task-row:hover {
            background-color: rgba(67, 97, 238, 0.05);
        }

        .progress {
            height: 12px;
            border-radius: 10px;
            background-color: #e9ecef;
        }

        .progress-bar {
            border-radius: 10px;
            background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
            transition: width 0.5s ease;
        }

        .btn-primary {
            background: linear-gradient(120deg, var(--primary-color), var(--secondary-color));
            border: none;
            border-radius: 8px;
            font-weight: 600;
            transition: all 0.3s;
        }

        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(67, 97, 238, 0.4);
        }

        .btn-success {
            background: linear-gradient(120deg, var(--success-color), #3a9b3a);
            border: none;
            border-radius: 6px;
            transition: all 0.3s;
        }

        .btn-success:hover {
            transform: scale(1.05);
        }

        .btn-warning {
            background: linear-gradient(120deg, var(--warning-color), #e68a00);
            border: none;
            border-radius: 6px;
            transition: all 0.3s;
        }

        .form-control,
        .form-select {
            border-radius: 8px;
            padding: 10px 15px;
            border: 1px solid #e2e8f0;
            transition: all 0.3s;
        }

        .form-control:focus,
        .form-select:focus {
            border-color: var(--primary-color);
            box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
        }

        .badge {
            border-radius: 6px;
            padding: 6px 12px;
            font-weight: 500;
        }

        .instructions-card {
            background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
        }

        .instructions-card ol {
            padding-left: 20px;
        }

        .instructions-card li {
            margin-bottom: 10px;
            padding-left: 5px;
        }

        .status-badge {
            font-size: 0.8rem;
            padding: 5px 10px;
        }

        .file-icon {
            font-size: 1.2rem;
            margin-right: 10px;
        }

        .task-table th {
            border-top: none;
            font-weight: 600;
            color: var(--light-text);
            background-color: rgba(248, 249, 250, 0.8);
        }

        .task-table td {
            vertical-align: middle;
        }

        .empty-state {
            padding: 40px 20px;
            text-align: center;
            color: var(--light-text);
        }

        .empty-state i {
            font-size: 3rem;
            margin-bottom: 15px;
            opacity: 0.5;
        }

        .settings-group {
            background-color: rgba(248, 249, 250, 0.5);
            border-radius: 10px;
            padding: 20px;
            margin-bottom: 20px;
        }

        .task-actions {
            display: flex;
            gap: 5px;
        }

        .task-file-info {
            display: flex;
            align-items: center;
        }

        .task-file-details {
            margin-left: 10px;
        }

        .task-file-name {
            font-weight: 500;
            margin-bottom: 2px;
        }

        .task-file-size {
            font-size: 0.8rem;
            color: var(--light-text);
        }

        @media (max-width: 768px) {
            .main-title {
                font-size: 2rem;
            }

            .card {
                margin-bottom: 20px;
            }

            .task-actions {
                flex-direction: column;
            }
        }
    </style>
</head>

<body>


    <header>
        <nav
            class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow fixed-top mb-3 py-1">
            <div class="container">
                <a class="navbar-brand" href="/" data-logo></a>
            </div>
        </nav>
    </header>
    <div class="container">
        <div class="row">
            <!-- 参数设置区域 -->
            <div class="col-lg-4">
                <!-- 文件选择区域 -->
                <div class="card">
                    <div class="card-header d-flex justify-content-between align-items-center">
                        <span><i class="bi bi-folder-plus me-2"></i>选择文件</span>
                    </div>
                    <div class="card-body">
                        <div class="file-drop-area" id="file-drop-area">
                            <i class="bi bi-cloud-arrow-up"></i>
                            <h5>拖放文件到此处</h5>
                            <p class="text-muted">支持音频和视频文件，单个文件不超过500MB</p>
                            <input type="file" id="file-input" class="d-none" multiple accept="audio/*,video/*">
                            <button class="btn btn-primary mt-2" id="browse-btn">
                                <i class="bi bi-folder2-open me-2"></i>浏览文件
                            </button>
                        </div>
                    </div>
                </div>

                <!-- 参数设置区域 -->
                <div class="card">
                    <div class="card-header">
                        <i class="bi bi-gear-fill me-2"></i>参数设置
                    </div>
                    <div class="card-body">
                        <div class="settings-group">
                            <form id="settings-form">
                                <div class="mb-3">
                                    <label for="language-select" class="form-label">语言选择</label>
                                    <select class="form-select" id="language-select">
                                        <option value="zh-CN" selected>中文（普通话）</option>
                                        <option value="en-US">英语（美国）</option>
                                        <option value="ja-JP">日语</option>
                                        <option value="ko-KR">韩语</option>
                                        <option value="fr-FR">法语</option>
                                    </select>
                                </div>

                                <div class="mb-3">
                                    <label for="model-select" class="form-label">模型选择</label>
                                    <select class="form-select" id="model-select">
                                        <option value="base" selected>基础模型</option>
                                        <option value="enhanced">增强模型</option>
                                        <option value="professional">专业模型</option>
                                    </select>
                                </div>

                                <div class="mb-3">
                                    <label for="slice-duration" class="form-label">切片时长（秒）</label>
                                    <input type="number" class="form-control" id="slice-duration" value="10" min="5"
                                        max="60">
                                    <div class="form-text">将长音频分割为指定时长的片段进行处理</div>
                                </div>

                                <div class="mb-3">
                                    <label for="silence-threshold" class="form-label">静音检测阈值（毫秒）</label>
                                    <input type="number" class="form-control" id="silence-threshold" value="500"
                                        min="100" max="2000">
                                    <div class="form-text">低于此阈值的静音段将被忽略</div>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>

                <!-- 操作说明 -->
                <div class="card instructions-card">
                    <div class="card-header">
                        <i class="bi bi-info-circle me-2"></i>使用说明
                    </div>
                    <div class="card-body">
                        <ol class="mb-0">
                            <li>选择需要转写的音频或视频文件</li>
                            <li>根据需要调整参数设置</li>
                            <li>点击"开始处理"按钮提交任务</li>
                            <li>在任务列表中查看处理进度</li>
                            <li>处理完成后下载SRT字幕文件</li>
                        </ol>
                    </div>
                </div>
            </div>

            <!-- 任务列表 -->
            <div class="col-lg-8">
                <div class="card">
                    <div class="card-header d-flex justify-content-between align-items-center">
                        <span><i class="bi bi-list-task me-2"></i>任务列表</span>
                        <div>
                            <!-- 连接状态指示器 -->
                            <span class="badge bg-default me-2">
                                <div id="connectionStatus" class="connection-status disconnected">
                                    <i class="bi bi-wifi-off"></i> 连接断开
                                </div>
                            </span>
                            <span class="badge bg-primary me-2" id="total-tasks">0 个任务</span>
                            <span class="badge bg-success" id="completed-tasks">0 个已完成</span>
                        </div>
                    </div>
                    <div class="card-body p-0">
                        <div class="table-responsive">
                            <table class="table table-hover mb-0 task-table">
                                <thead>
                                    <tr>
                                        <th width="35%">文件</th>
                                        <th width="15%">状态</th>
                                        <th width="25%">进度</th>
                                        <th width="25%">操作</th>
                                    </tr>
                                </thead>
                                <tbody id="task-list">
                                    <!-- 任务列表将在这里动态生成 -->
                                    <tr id="no-tasks-row">
                                        <td colspan="4" class="text-center py-5">
                                            <div class="empty-state">
                                                <i class="bi bi-hourglass-split"></i>
                                                <p>暂无任务，请先选择文件</p>
                                            </div>
                                        </td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>

                        <div class="d-grid m-4">
                            <button class="btn btn-primary" id="start-task-btn">
                                <i class="bi bi-play-fill me-2"></i> 开始处理所有任务
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2025 - <a href="https://www.koudingke.cn" target="_blank">抠丁客</a>
        </div>
    </footer>
    <script src="/js/bundle"></script>
    <script>
        // 存储任务列表
        let tasks = [];
        let connection = null;
        let connectionId = null;
        let isConnected = false;

        // DOM 元素
        const fileInput = document.getElementById('file-input');
        const browseBtn = document.getElementById('browse-btn');
        const fileDropArea = document.getElementById('file-drop-area');
        const taskList = document.getElementById('task-list');
        const noTasksRow = document.getElementById('no-tasks-row');
        const startTaskBtn = document.getElementById('start-task-btn');
        const settingsForm = document.getElementById('settings-form');
        const totalTasks = document.getElementById('total-tasks');
        const completedTasks = document.getElementById('completed-tasks');
        const connectionStatus = document.getElementById('connectionStatus');

        // 点击浏览按钮触发文件选择
        browseBtn.addEventListener('click', () => {
            fileInput.click();
        });

        // 文件选择处理
        fileInput.addEventListener('change', (e) => {
            handleFiles(e.target.files);
        });

        // 拖放区域事件处理
        fileDropArea.addEventListener('dragover', (e) => {
            e.preventDefault();
            fileDropArea.classList.add('active');
        });

        fileDropArea.addEventListener('dragleave', () => {
            fileDropArea.classList.remove('active');
        });

        fileDropArea.addEventListener('drop', (e) => {
            e.preventDefault();
            fileDropArea.classList.remove('active');
            if (e.dataTransfer.files.length) {
                handleFiles(e.dataTransfer.files);
            }
        });

        // 处理选中的文件
        function handleFiles(files) {
            // 过滤出音频和视频文件
            const mediaFiles = Array.from(files).filter(file =>
                file.type.startsWith('audio/') || file.type.startsWith('video/') ||
                ['.mp3', '.mp4', '.wav', '.avi', '.mov', '.flac', '.aac', '.wma', '.m4a']
                    .some(ext => file.name.toLowerCase().endsWith(ext))
            );

            if (mediaFiles.length === 0) {
                toastr.error('请选择音频或视频文件！');
                return;
            }

            // 为每个文件创建任务
            mediaFiles.forEach(file => {
                const taskId = 'task-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
                const task = {
                    id: taskId,
                    fileName: file.name,
                    fileSize: file.size,
                    fileType: file.type,
                    status: '等待中',
                    progress: 0,
                    file: file,
                    serverTaskId: null
                };

                tasks.push(task);
                addTaskToTable(task);
            });

            updateTaskCounters();
        }

        // 格式化文件大小
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }

        // 添加任务到表格
        function addTaskToTable(task) {
            if (noTasksRow) noTasksRow.remove();

            const row = document.createElement('tr');
            row.className = 'task-row';
            row.id = task.id;

            const fileIcon = task.fileType.startsWith('audio/') ||
                ['.mp3', '.wav', '.flac', '.aac', '.wma', '.m4a']
                    .some(ext => task.fileName.toLowerCase().endsWith(ext)) ?
                'bi-file-earmark-music text-primary' :
                'bi-file-earmark-play text-danger';

            row.innerHTML = `
                <td>
                    <div class="task-file-info">
                        <i class="bi ${fileIcon} file-icon"></i>
                        <div class="task-file-details">
                            <div class="task-file-name text-truncate" title="${task.fileName}">${task.fileName}</div>
                            <div class="task-file-size">${formatFileSize(task.fileSize)}</div>
                        </div>
                    </div>
                </td>
                <td>
                    <span class="badge status-badge bg-secondary">${task.status}</span>
                </td>
                <td>
                    <div class="progress">
                        <div class="progress-bar" role="progressbar" style="width: ${task.progress}%" 
                             aria-valuenow="${task.progress}" aria-valuemin="0" aria-valuemax="100">
                            ${task.progress}%
                        </div>
                    </div>
                </td>
                <td>
                    <div class="task-actions">
                        <button class="btn btn-sm btn-primary copy-btn" data-bs-toggle="tooltip" data-bs-placement="top" title="复制纯文本" disabled>
                            <i class="bi bi-copy"></i>
                        </button>
                        <button class="btn btn-sm btn-success download-btn" data-bs-toggle="tooltip" data-bs-placement="top" title="下载 SRT 字幕" disabled>
                            <i class="bi bi-download"></i> 
                        </button>
                        <button class="btn btn-sm btn-danger remove-task" data-bs-toggle="tooltip" data-bs-placement="top" title="从任务列表删除">
                            <i class="bi bi-trash"></i>
                        </button>
                    </div>
                </td>
            `;

            taskList.appendChild(row);

            // 创建 tooltip
            var tooltipTriggerList = [].slice.call(row.querySelectorAll('[data-bs-toggle="tooltip"]'))
            var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
                return new bootstrap.Tooltip(tooltipTriggerEl)
            })

            // 添加删除任务事件
            const removeBtn = row.querySelector('.remove-task');
            removeBtn.addEventListener('click', () => {
                // 先隐藏所有 tooltip
                tooltipList.forEach(tip => tip.hide());

                // 延迟销毁和删除行
                setTimeout(() => {
                    tooltipList.forEach(tip => tip.dispose());
                    removeTask(task.id);
                }, 300);
            });

            // 添加下载事件
            const downloadBtn = row.querySelector('.download-btn');
            downloadBtn.addEventListener('click', () => {
                downloadTaskResult(task.id);
            });

            // 添加复制事件
            const copyBtn = row.querySelector('.copy-btn');
            copyBtn.addEventListener('click', () => {
                copyTaskText(task.id);
            });
        }

        // 开始处理所有任务
        startTaskBtn.addEventListener('click', async () => {
            const waitingTasks = tasks.filter(task => task.status === '等待中');

            if (waitingTasks.length === 0) {
                toastr.info('没有等待处理的任务！');
                return;
            }

            // 获取参数设置
            const settings = getCurrentSettings();

            // 禁用开始处理按钮
            startTaskBtn.disabled = true;
            startTaskBtn.innerHTML = '<i class="bi bi-hourglass-split me-2"></i> 处理中...';

            // 依次处理每个任务
            for (const task of waitingTasks) {
                await processTask(task, settings);
            }

            // 重新启用开始处理按钮
            startTaskBtn.disabled = false;
            startTaskBtn.innerHTML = '<i class="bi bi-play-fill me-2"></i> 开始处理所有任务';
        });

        // 获取当前设置
        function getCurrentSettings() {
            return {
                language: document.getElementById('language-select').value,
                model: document.getElementById('model-select').value,
                sliceDuration: parseInt(document.getElementById('slice-duration').value),
                silenceThreshold: parseInt(document.getElementById('silence-threshold').value)
            };
        }

        // 处理单个任务
        async function processTask(task, settings) {
            try {
                // 更新任务状态为上传中
                task.status = '上传中';
                updateTaskRow(task);

                // 创建FormData对象
                const formData = new FormData();
                formData.append('file', task.file);
                formData.append('connectionId', connectionId);
                formData.append('language', settings.language);
                formData.append('model', settings.model);
                formData.append('sliceDuration', settings.sliceDuration);
                formData.append('silenceThreshold', settings.silenceThreshold);

                // 发送上传请求
                const response = await fetch(`${API_BASE_URL}/upload`, {
                    method: 'POST',
                    body: formData,
                    /*
                    // 监听上传进度
                    onUploadProgress: (progressEvent) => {
                        const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
                        task.uploadProgress = percentCompleted;
                        // 上传进度占总进度的40%
                        task.progress = Math.round(percentCompleted * 0.4);
                        updateTaskRow(task);
                    }
                    */
                });

                if (!response.ok) {
                    throw new Error(`上传失败: ${response.statusText}`);
                }

                const result = await response.json();

                if (result.success) {
                    task.status = '处理中';
                    task.serverTaskId = result.data.taskId;
                    task.progress = result.data.progress;
                    updateTaskRow(task);
                } else {
                    throw new Error(result.message || '上传失败');
                }
            } catch (error) {
                console.error('处理任务时出错:', error);
                task.status = '失败';
                updateTaskRow(task);
                toastr.error(`任务处理失败: ${error.message}`);
            }
        }

        // 更新任务行显示
        function updateTaskRow(task) {
            const row = document.getElementById(task.id);
            if (!row) return;

            const statusBadge = row.querySelector('.badge');
            const progressBar = row.querySelector('.progress-bar');

            // 更新状态
            statusBadge.textContent = task.status;

            // 根据状态更新颜色
            if (task.status === '等待中') {
                statusBadge.className = 'badge status-badge bg-secondary';
            } else if (task.status === '上传中') {
                statusBadge.className = 'badge status-badge bg-warning';
            } else if (task.status === '处理中') {
                statusBadge.className = 'badge status-badge bg-primary';
            } else if (task.status === '已完成') {
                statusBadge.className = 'badge status-badge bg-success';
            } else if (task.status === '失败') {
                statusBadge.className = 'badge status-badge bg-danger';
            }

            // 更新进度条
            progressBar.style.width = `${task.progress}%`;
            progressBar.textContent = `${task.progress}%`;
            progressBar.setAttribute('aria-valuenow', task.progress);
        }

        // 删除任务
        function removeTask(taskId) {
            const task = tasks.find(t => t.id === taskId);
            if (task && task.serverTaskId && connection && isConnected) {
                // 取消订阅该任务
                connection.invoke("UnsubscribeFromTask", task.serverTaskId)
                    .catch(err => console.error('取消订阅失败:', err));
            }

            tasks = tasks.filter(task => task.id !== taskId);
            const row = document.getElementById(taskId);
            if (row) row.remove();

            if (tasks.length === 0) {
                taskList.appendChild(noTasksRow);
            }

            updateTaskCounters();
        }

        // 下载任务结果
        async function downloadTaskResult(taskId) {
            const task = tasks.find(t => t.id === taskId);
            if (!task || !task.serverTaskId) return;

            try {
                const response = await fetch(`${API_BASE_URL}/task/${task.serverTaskId}/download`);
                if (!response.ok) {
                    throw new Error(`下载失败: ${response.statusText}`);
                }

                const blob = await response.blob();
                const url = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = `${task.fileName.split('.')[0]}.srt`;
                document.body.appendChild(a);
                a.click();
                window.URL.revokeObjectURL(url);
                document.body.removeChild(a);
            } catch (error) {
                console.error('下载失败:', error);
                toastr.error('下载失败: ' + error.message);
            }
        }

        // 复制任务文本
        async function copyTaskText(taskId) {
            const task = tasks.find(t => t.id === taskId);
            if (!task || !task.serverTaskId) return;

            try {
                const response = await fetch(`${API_BASE_URL}/task/${task.serverTaskId}/text`);
                if (!response.ok) {
                    throw new Error(`获取文本失败: ${response.statusText}`);
                }

                const data = await response.json();
                if (data.success) {
                    await navigator.clipboard.writeText(data.text);
                    toastr.success('文本已复制到剪贴板');
                } else {
                    throw new Error(data.message || '获取文本失败');
                }
            } catch (error) {
                console.error('复制失败:', error);
                toastr.error('复制失败: ' + error.message);
            }
        }

        // 更新任务计数器
        function updateTaskCounters() {
            totalTasks.textContent = `${tasks.length} 个任务`;
            completedTasks.textContent = `${tasks.filter(t => t.status === '已完成').length} 个已完成`;
        }

        // 初始化 SignalR 连接
        function initSignalR() {
            connection = new signalR.HubConnectionBuilder()
                .withUrl("/ws/transcription")
                .withAutomaticReconnect({
                    nextRetryDelayInMilliseconds: retryContext => {
                        // 指数退避策略
                        return Math.min(1000 * Math.pow(2, retryContext.previousRetryCount), 30000);
                    }
                })
                .configureLogging(signalR.LogLevel.Information)
                .build();

            // 连接事件处理
            connection.on("Connected", (data) => {
                console.log('SignalR 连接已建立:', data);
                isConnected = true;
                connectionId = data.connectionId;
                updateConnectionStatus(true);
            });

            connection.on("ProgressUpdate", (data) => {
                console.log('收到进度更新:', data);
                updateTaskFromSignalR(data);
            });

            connection.on("Subscribed", (data) => {
                console.log('订阅成功:', data);
            });

            connection.on("Unsubscribed", (data) => {
                console.log('取消订阅成功:', data);
            });

            connection.on("MessageReceived", (data) => {
                console.log('服务器消息:', data);
            });

            connection.onreconnecting((error) => {
                console.log('SignalR 重新连接中...', error);
                isConnected = false;
                updateConnectionStatus(false, true);
            });

            connection.onreconnected((connId) => {
                console.log('SignalR 重新连接成功:', connId);
                isConnected = true;
                connectionId = connId;
                updateConnectionStatus(true);

                // 重新订阅所有任务
                tasks.forEach(task => {
                    if (task.serverTaskId) {
                        connection.invoke("SubscribeToTask", task.serverTaskId)
                            .catch(err => console.error('重新订阅失败:', err));
                    }
                });
            });

            connection.onclose((error) => {
                console.log('SignalR 连接已关闭', error);
                isConnected = false;
                updateConnectionStatus(false);
            });

            // 启动连接
            connection.start().catch(err => {
                console.error('SignalR 连接失败:', err);
                isConnected = false;
                updateConnectionStatus(false);
            });
        }

        // 更新连接状态显示
        function updateConnectionStatus(connected, reconnecting = false) {
            if (reconnecting) {
                connectionStatus.className = 'connection-status reconnecting';
                connectionStatus.innerHTML = '<i class="bi bi-arrow-repeat"></i> 重新连接中...';
            } else if (connected) {
                connectionStatus.className = 'connection-status connected';
                connectionStatus.innerHTML = '<i class="bi bi-wifi"></i> 连接正常';
            } else {
                connectionStatus.className = 'connection-status disconnected';
                connectionStatus.innerHTML = '<i class="bi bi-wifi-off"></i> 连接断开';
            }
        }

        // 从 SignalR 更新任务
        function updateTaskFromSignalR(data) {
            const task = tasks.find(t => t.serverTaskId === data.taskId);
            if (!task) {
                return;
            }

            // 更新任务状态
            task.status = data.status;
            task.progress = data.progress;
            task.error = data.error;

            // 更新 UI
            updateTaskRow(task);
            updateTaskCounters();

            // 如果任务完成，启用下载按钮
            if (task.status === '已完成') {
                const row = document.getElementById(task.id);
                if (row) {
                    const downloadBtn = row.querySelector('.download-btn');
                    const copyBtn = row.querySelector('.copy-btn');
                    if (downloadBtn) downloadBtn.disabled = false;
                    if (copyBtn) copyBtn.disabled = false;
                }
            }
        }

        window.addEventListener('DOMContentLoaded', () => {
            initSignalR();
        });
    </script>

</html>